﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BLL.Relations.VectorDistances;
using DO.Clustering;

namespace BLL.Clustering
{
    public class DistanceManager
    {
        public static Dictionary<int, Dictionary<int, double>> CalculateDocumentDistances(
            WeightedFeatureSimilarityCalculator distanceCalc,
            Dictionary<int, Dictionary<int, FeatureFreq>> docTermFreqs)
        {
            Dictionary<int, Dictionary<int, double>> distances = new Dictionary<int, Dictionary<int, double>>();
            List<int> projIDs = new List<int>();
            projIDs.AddRange(docTermFreqs.Keys);
            for (int i = 0; i < projIDs.Count; i++)
            {
                int fromProjID = projIDs[i];
                Dictionary<int, FeatureFreq> featureFreqs1 = docTermFreqs[fromProjID];
                Dictionary<int, double> toDistances = new Dictionary<int, double>();
                for (int k = i + 1; k < projIDs.Count; k++)
                {
                    int toProjID = projIDs[k];
                    Dictionary<int, FeatureFreq> featureFreqs2 = docTermFreqs[toProjID];
                    Dictionary<int, int> contributions = new Dictionary<int, int>();
                    double distance = distanceCalc.CalculateDistance(featureFreqs1, featureFreqs2, ref contributions);
                    toDistances.Add(toProjID, distance);
                }
                distances.Add(fromProjID, toDistances);
            }
            return distances;
        }

        public static void UpdateClusterDistances(
            WeightedFeatureSimilarityCalculator calc, int newClusterID,
            Dictionary<int, Dictionary<int, FeatureFreq>> docFeatFreqs,
            Dictionary<int,double> termWeights,
            Dictionary<int, Dictionary<int, double>> clusterDocDistances,
            Dictionary<int, Dictionary<int, double>> clusterClusterDistances,
            Dictionary<int, Cluster> clusterResult,
            List<int> remainingDocIDs,
            Dictionary<int, List<int>> clusterTerms)
        {
            Dictionary<int, FeatureFreq> termFreqs1 = new Dictionary<int, FeatureFreq>();
            List<int> docIDs = GetDocsForCluster(newClusterID, clusterResult);

            foreach (int docID1 in docIDs)
            {
                Dictionary<int, FeatureFreq> docTerms1 = docFeatFreqs[docID1];
                foreach (int termID in docTerms1.Keys)
                {
                    if (termFreqs1.ContainsKey(termID))
                    {
                        FeatureFreq docTermFreq1 = termFreqs1[termID];
                        docTermFreq1.Count += docTerms1[termID].Count;
                        termFreqs1[termID] = docTermFreq1;
                    }
                    else
                    {
                        termFreqs1.Add(termID, docTerms1[termID]);
                    }
                }
            }
            // termFreq1.Add(termID1, 1);


            // update cluster - doc distances 
            foreach (int docID in remainingDocIDs)
            {
                Dictionary<int, FeatureFreq> termFreqs2 = docFeatFreqs[docID];
                //Dictionary<int,int> termFreq2=new Dictionary<int, int>();
                //foreach(int termID2 in termFreqs2.Keys)
                //{
                //    termFreq2.Add(termID2,1);
                //}
                Dictionary<int, int> sharedTermIDs = new Dictionary<int, int>();
                double distance = calc.CalculateDistance(
                        termFreqs1, termFreqs2, termWeights,
                        true, 0, ref sharedTermIDs);
                if (clusterDocDistances.ContainsKey(newClusterID))
                {
                    Dictionary<int, double> toDocDistances = clusterDocDistances[newClusterID];
                    if (toDocDistances.ContainsKey(docID))
                        toDocDistances[docID] = distance;
                    else
                        toDocDistances.Add(docID, distance);
                    clusterDocDistances[newClusterID] = toDocDistances;
                }
                else
                {
                    Dictionary<int, double> toDocDistances = new Dictionary<int, double>();
                    toDocDistances.Add(docID, distance);
                    clusterDocDistances.Add(newClusterID, toDocDistances);
                }
            }

            // update cluster - cluster distances 
            foreach (int clusterID in clusterTerms.Keys)
            {
                if (clusterID != newClusterID)
                {
                    Dictionary<int, FeatureFreq> termFreqs2 = new Dictionary<int, FeatureFreq>();
                    Cluster clusterNode2 = clusterResult[clusterID];
                    foreach (int docID2 in clusterNode2.DocIDs)
                    {
                        Dictionary<int, FeatureFreq> docTerms = docFeatFreqs[docID2];
                        foreach (int termID in docTerms.Keys)
                        {
                            if (termFreqs2.ContainsKey(termID))
                            {
                                FeatureFreq docTermFreq = termFreqs2[termID];
                                docTermFreq.Count += docTerms[termID].Count;
                                termFreqs2[termID] = docTermFreq;
                            }
                            else
                            {
                                termFreqs2.Add(termID, docTerms[termID]);
                            }
                        }
                    }
                    Dictionary<int, int> sharedTermIDs = new Dictionary<int, int>();
                    //double distance = _DistanceCalculator.CalculateDistance(termFreq1, termFreq2, ref sharedTermIDs);
                    double distance = calc.CalculateDistance(
                            termFreqs1, termFreqs2, termWeights,
                            true, 0, ref sharedTermIDs);
                    if (clusterClusterDistances.ContainsKey(clusterID))
                    {
                        Dictionary<int, double> toClusterDistances = clusterClusterDistances[clusterID];
                        if (toClusterDistances.ContainsKey(newClusterID))
                            toClusterDistances[newClusterID] = distance;
                        else
                            toClusterDistances.Add(newClusterID, distance);
                        clusterClusterDistances[clusterID] = toClusterDistances;
                    }
                    else
                    {
                        Dictionary<int, double> toClusterDistances = new Dictionary<int, double>();
                        toClusterDistances.Add(newClusterID, distance);
                        clusterClusterDistances.Add(clusterID, toClusterDistances);
                    }
                }
            }
        }

        public static double GetClusterDiameter(List<int> includedDocIDs,
            Dictionary<int, Dictionary<int, double>> entityDistances)
        {
            double maxDistance = 0;
            for (int i = 0; i < includedDocIDs.Count; i++)
            {
                int fromDocID = includedDocIDs[i];
                for (int k = i + 1; k < includedDocIDs.Count; k++)
                {
                    int toDocID = includedDocIDs[k];
                    if (entityDistances.ContainsKey(fromDocID) &&
                        entityDistances[fromDocID].ContainsKey(toDocID) &&
                        entityDistances[fromDocID][toDocID] > maxDistance)
                        maxDistance = entityDistances[fromDocID][toDocID];
                    else if (entityDistances.ContainsKey(toDocID) &&
                        entityDistances[toDocID].ContainsKey(fromDocID) &&
                        entityDistances[toDocID][fromDocID] > maxDistance)
                        maxDistance = entityDistances[toDocID][fromDocID];
                }
            }
            return maxDistance;
        }

        private static List<int> GetDocsForCluster(int clusterID, 
            Dictionary<int, Cluster> clusterResult)
        {
            Cluster cluster = clusterResult[clusterID];
            List<int> docIDs = new List<int>();
            if (cluster.DocIDs != null && cluster.DocIDs.Count > 0)
                docIDs.AddRange(cluster.DocIDs);
            if (cluster.ChildClusterIDs != null && cluster.ChildClusterIDs.Count > 0)
            {
                foreach (int childClusterID in cluster.ChildClusterIDs)
                {
                    List<int> childDocIDs = GetDocsForCluster(childClusterID, clusterResult);
                    foreach (int childDocID in childDocIDs)
                    {
                        if (!docIDs.Contains(childDocID))
                            docIDs.Add(childDocID);
                    }
                }
            }
            return docIDs;
        }

        public static Dictionary<int, Dictionary<int, double>> CalculateTermTermDistances(
            WeightedFeatureSimilarityCalculator distanceCalc,
            List<int> topTermIDs,
            Dictionary<int, Dictionary<int, FeatureFreq>> docTermFreqs)
        {
            Dictionary<int, Dictionary<int, double>> distances = 
                new Dictionary<int, Dictionary<int, double>>();
            Dictionary<int, Dictionary<int,FeatureFreq>> termDocFeats=
                new Dictionary<int, Dictionary<int, FeatureFreq>>();
            foreach(int termID in topTermIDs)
            {
                Dictionary<int, FeatureFreq> docFeats = new Dictionary<int, FeatureFreq>();
                foreach(int docID in docTermFreqs.Keys)
                {
                    if(docTermFreqs[docID].ContainsKey(termID))
                    {
                        FeatureFreq docFeat = new FeatureFreq()
                                                  {
                                                      Count = docTermFreqs[docID][termID].Count,
                                                      FeatureID = docID,
                                                      Name = null,
                                                      Weight = docTermFreqs[docID][termID].Weight
                                                  };
                        docFeats.Add(docID, docFeat);
                    }
                }
                termDocFeats.Add(termID, docFeats);
            }
            for (int i = 0; i < topTermIDs.Count; i++)
            {
                int fromTermID = topTermIDs[i];
                Dictionary<int, FeatureFreq> featureFreqs1 = termDocFeats[fromTermID];
                Dictionary<int, double> toDistances = new Dictionary<int, double>();
                for (int k = 0; k < topTermIDs.Count; k++)
                {
                    int toTermID = topTermIDs[k];
                    if(fromTermID==toTermID)
                        continue;
                    Dictionary<int, FeatureFreq> featureFreqs2 = termDocFeats[toTermID];
                    Dictionary<int, int> contributions = new Dictionary<int, int>();
                    double distance = distanceCalc.CalculateDistance(featureFreqs1, featureFreqs2, ref contributions);
                    toDistances.Add(toTermID, distance);
                }
                distances.Add(fromTermID, toDistances);
            }
            return distances;
        }
    }
}
